Comment utiliser le Test-Driven Development sur Salesforce pour vos intégrations API ?
Avis d'experts
04 février 2026
Les Tests Unitaires sont en général vus par les développeurs Salesforce comme une contrainte et une corvée. Trop souvent, ils sont relégués à la fin du projet, alors qu’ils peuvent aider à écrire du code solide. Pire parfois, lorsqu’ils n’ont pas été anticipés, ils deviennent problématiques à écrire.
Pour l’intégration avec des API externes, ils peuvent faciliter le développement, en permettant par exemple de les écrire même lorsque l’on n’a pas encore accès à l’API, pour peu que l’on connaisse la structure des données que l’on récupérera.
Dans notre exemple, nous supposerons que nous avons une API Magellan externe.
Le principe sous-jacent est d’utiliser les classes de tests pour valider directement le développement des classes métiers.
On utilisera les Static Resources pour simuler exactement le retour de l’API. L’utilisation des Static Resources est d’une part plus simple que de mettre le contenu dans une simple string. Elle permet d’identifier facilement tous les cas de figure que l’on peut rencontrer avec l’API. Elle rend plus simple le déploiement. Pour tester une évolution de l’API, il suffit de mettre à jour la ressource ou d’en ajouter une nouvelle.
Étape 1 : la classe technique Magellan APIConnector
Dans un premier temps, nous allons écrire la classe technique gérant l’appel API proprement dit. Cette classe encapsule l’appel HTTP vers l’API Magellan. Elle gère les différents cas de réponse (succès, erreur, données non trouvées, exception). Bien sûr il ne faudra pas oublier les Remote Site Settings. Pour l’identification, en général on utilisera les Named Credentials.
public with sharing class Magellan APIConnector {
/**
* Un exemple d'appel API REST vers un service externe.
* Le service retourne des données au format JSON.
*/
public static Map<String, Object> callMagellan API(String Magellan Id) {
Map<String, Object> result = new Map<String, Object>();
String endpoint = 'https://api.Magellan .com/v1/Magellan Id/' + Magellan Id;
HttpRequest request = new HttpRequest();
request.setEndpoint(endpoint);
request.setMethod('GET');
request.setHeader('Accept', 'application/json');
Http http = new Http();
HttpResponse response;
try {
response = http.send(request);
if (response.getStatusCode() == 200) {
result.put('status', 'found');
Map<String, Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
result.put('data', responseMap);
return result;
} else if (response.getStatusCode() == 404) {
result.put('status', 'not_found');
return result;
} else {
result.put('status', 'error');
result.put('error', 'Error while calling API ' + response.getStatusCode() + ' - ' + response.getBody());
return result;
}
} catch (Exception exc) {
result.put('status', 'error');
result.put('error', 'Error while calling API ' + response.getStatusCode() + ' - ' + response.getBody());
result.put('error', 'Error while calling API ' + exc.getMessage());
return result;
}
}
}
Étape 2 : créer une classe implémentant MockHttpResponseGenerator
Avant d’écrire notre classe de Test nous avons besoin d’implémenter une HttpCalloutMock qui permettra de simuler l’appel à l’API.
Le plus efficace est de créer une seule classe et d’anticiper les principaux scénarios (en fonction de l’API), soit dans notre exemple :
- Réponse 200 avec des données valides
- Réponse 404 (données non trouvées)
- Réponse 500 (erreur serveur)
- Exception (ex. JSON invalide ou problème réseau)
/**
* Cette classe est une implémentation fictive de l'interface HttpCalloutMock.
* Elle est utilisée pour simuler des réponses HTTP lors des tests.
**/
@isTest
global with sharing class MockHttpResponseGenerator implements HttpCalloutMock{
public Integer statusCode;
public String body = '';
public String contentType = 'application/json';
public String status = '';
/**
* Constructeur pour générer une réponse HTTP, lorsque nous ne testons que le status code
*/
global MockHttpResponseGenerator(Integer statusCode) {
this.statusCode = statusCode;
}
/**
* Constructeur pour générer une réponse HTTP complète, avec le status code et le body
*/
global MockHttpResponseGenerator(Integer statusCode, String body) {
this.statusCode = statusCode;
this.body = body;
}
/**
* Implémentation de la méthode respond de l'interface HttpCalloutMock
*/
global HTTPResponse respond(HTTPRequest req) {
HttpResponse response = new HttpResponse();
response.setHeader('Content-Type', this.contentType);
response.setBody(this.body);
response.setStatusCode(this.statusCode);
response.setStatus(this.status);
return response;
}
}
Étape 3 : la classe de test Magellan APIConnector_Test
Maintenant, avec la classe de test, nous allons vérifier le bon fonctionnement sans avoir besoin de nous connecter à l’API.
Vérifier que tout fonctionne avant même de connecter réellement le service a de nombreux avantages :
- Pouvoir tester quand les environnements ne sont pas disponibles
- Éviter de polluer l’environnement de test avec des erreurs
- Vérifier tous les scénarios
- Maximiser la couverture du code
L’astuce principale, consiste ici à créer des Static Resources contenant le json retourné par l’API, pour couvrir les différents scénarios.
/**
* Le but de la classe est de fournir d'une part la couverture de code maximale
* et d'autre part de tester les différents scénarios possibles lors d'un appel API
* avec les données utilisées
*/
@isTest
public with sharing class Magellan APIConnector_Test {
/**
* Test d'un appel API réussi (status 200) avec des données retournées
*/
@isTest
static void test_callMagellan APIFound() {
/**
* Nous utilisons une ressource statique pour stocker la réponse JSON de l'API.
* Cela nous permet de simuler une réponse réaliste sans dépendre d'une API externe
*/
StaticResource staticResource = [SELECT Body FROM StaticResource WHERE Name = 'test_api_Magellan ' LIMIT 1];
String jsonResponse = staticResource.Body.toString();
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator(200, jsonResponse));
Test.startTest();
Map<String, Object> result = Magellan APIConnector.callMagellan API('1234567890');
Test.stopTest();
System.assertEquals('found', result.get('status'));
System.assertNotEquals(null, result.get('data'));
/**
* Nous vérifions que la réponse contient bien les données attendues
* souvent il s'agira de données plus complexes qu'une simple Map
* et l'on pourra utiliser les règles métier pour valider les données
* reçues de l'API
*/
Map<String, Object> data = (Map<String, Object>) result.get('data');
System.assertNotEquals(null, data.get('myImportantData'));
Map<String, Object> myImportantData = (Map<String, Object>) data.get('myImportantData');
System.assertEquals('area51', myImportantData.get('importantField'));
}
/**
* Test d'un appel API avec un status 404 (not found)
* l'appel est réussi mais l'API ne trouve pas de données
*/
@isTest
static void test_callMagellan APINotFound() {
String bodyResponse = '{"statut":404,"message":"No data found for the given Magellan Id"}';
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator(404, bodyResponse));
Test.startTest();
Map<String, Object> result = Magellan APIConnector.callMagellan API('1234567890');
Test.stopTest();
System.assertEquals('not_found', result.get('status'));
System.assertEquals(null, result.get('data'));
}
/**
* Test d'un appel API avec un status 500 (internal server error)
* le serveur de l'API a rencontré une erreur
*/
@isTest
static void test_callMagellan APIError500() {
String bodyResponse = '{"statut":500,"message":"Internal Server Error"}';
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator(500, bodyResponse));
Test.startTest();
Map<String, Object> result = Magellan APIConnector.callMagellan API('1234567890');
Test.stopTest();
System.assertEquals('error', result.get('status'));
System.assertEquals(null, result.get('data'));
}
/**
* Test d'un appel API qui génère une exception
*/
@isTest
static void test_callMagellan APIException() {
/**
* Pour simuler une exception nous utilisons un body qui n'est pas du JSON valide
* mais l'exception peut aussi être générée par d'autres causes
* comme un Remote Site non configuré, un timeout, etc.
*/
String bodyResponse = 'json error';
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator(200, bodyResponse));
Test.startTest();
Map<String, Object> result = Magellan APIConnector.callMagellan API('1234567890');
Test.stopTest();
System.assertEquals('error', result.get('status'));
System.assertEquals(null, result.get('data'));
}
}
Étape 4 : la classe consommant l’API
Maintenant que la classe technique a été écrite et validée, il est simple d’écrire la classe métier.
En règle générale, l’appel à une API externe est utilisé par une ou des classes métiers qui feront un traitement particulier avec les données récupérées.
public class Magellan APIConsumer {
/**
* Exemple d'une méthode consommant l'API Magellan .
*/
public static void exampleCall(String Magellan Id) {
Map<String, Object> result = Magellan APIConnector.callMagellan API(Magellan Id);
if (result.get('status') == 'found') {
// faire un traitement avec les données reçues
}
if (result.get('status') == 'not_found') {
// traiter une erreur fonctionnelle
}
if (result.get('status') == 'error') {
// traiter une erreur technique
}
}
}
Étape 5 : la classe de test métier
Finalement, en utilisant la même approche que pour la classe technique, nous pouvons écrire notre classe de test métier qui va permettre de valider l’intégration correcte de l’API.
En utilisant les Static Resources, on peut tester une vraie cinématique avec des données réalistes. Comme on va utiliser les JSON fournis par l’API, il va être possible de tester les classes sans avoir besoin d’un véritable environnement.
@isTest
public class Magellan APIConsumer_Test {
@isTest
static void test_exampleCallUS1() {
Test.startTest();
Test.stopTest();
StaticResource staticResource = [SELECT Body FROM StaticResource WHERE Name = 'test_api_Magellan _us1' LIMIT 1];
String jsonResponse = staticResource.Body.toString();
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator(200, jsonResponse));
Test.startTest();
Magellan APIConsumer.exampleCall('1234567890');
Test.stopTest();
// insérer ici des assertions pour vérifier les règles métier sont respectées
// pour la USER STORY 1
}
@isTest
static void test_exampleCallUS2() {
Test.startTest();
Test.stopTest();
StaticResource staticResource = [SELECT Body FROM StaticResource WHERE Name = 'test_api_Magellan _us2' LIMIT 1];
String jsonResponse = staticResource.Body.toString();
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator(200, jsonResponse));
Test.startTest();
Magellan APIConsumer.exampleCall('1234567890');
Test.stopTest();
// insérer ici des assertions pour vérifier les règles métier sont respectées
// pour la USER STORY 2
}
}
En résumé
Le développement orienté test (TDD) appliqué aux intégrations API sur Salesforce est une approche puissante pour garantir la qualité, la fiabilité et la maintenabilité du code. En simulant les réponses de l’API, vous pouvez :
- Définir le comportement attendu
- Tester tous les scénarios
- Livrer du code prêt à l’emploi, même si l’API ne l’est pas encore
Développer les tests en premier, c’est anticiper les problèmes et construire du solide !
Un décryptage de notre expert
Bruno NGUYEN HUU
Envie d'aller plus loin avec nous
Rencontrez nos experts pour plus d’informations sur les solutions Salesforce.